/*
 * Copyright (c) 2024 Black Sesame Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "msgbox-tools/MsgBoxGenerator.h"
#include "FDModel/FDModelManager.h"
#include "FDModel/FDEnumerator.h"
#include "msgbox-tools/BareMetalStubGenerator.h"
#include "msgbox-tools/BareMetalProxyGenerator.h"

#include <fstream>
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;

namespace BstIdl
{

bool MsgBoxGenerator::generate()
{
    if (m_destDir.empty())
        return false;

    if(fs::exists(m_destDir))
        fs::remove_all(m_destDir);

    auto path = "/";
    m_folderPath = mk_dir(m_destDir, path);
    if (m_folderPath.empty())
        return false;

    auto &fdmodels = BstIdl::FDModelManager::getInstance();
    if (fdmodels.size() == 0)
    {
        CERR << "No valid models!" << ENDL;
        return false;
    }

    for (const auto &model : fdmodels.getModelList())
    {
        for (const auto &element : model->getDeployments())
        {
            auto spec = element->getSpec();
            if (!spec || spec->getName() != "com.bst.ipc.deployment")
                continue;
            if (auto ptr = std::dynamic_pointer_cast<FDExtensionRoot>(element))
            {
                if (ptr->getTag() != "provider")
                    continue;
                if (!validateProvider(ptr))
                {
                    CERR << "Error provider deployment." << ENDL;
                    return false;
                }
                if (m_rte == "BareMetal")
                    m_rtestr = "IPC_RTE_BAREMETAL";
                else if (m_rte == "Posix")
                    m_rtestr = "IPC_RTE_POSIX";
                else if (m_rte == "LinuxKernel")
                    m_rtestr = "IPC_RTE_KERNEL";
                else if (m_rte == "RTOS")
                    m_rtestr = "IPC_RTE_RTOS";
                else
                {
                    CERR << "Unknown RTE : " << m_rte << ENDL;
                    return false;
                }
                bool ret = false;
                if (m_providerType == "Server")
                {
                    std::cout << "Generating " << m_rte << " server code ..." << ENDL;
                    for (const auto &ins : ptr->getElements())
                    {
                        if (!ins || ins->getTag() != "instance")
                            continue;
                        auto gen = std::make_shared<StubGenerator>(m_destDir, m_srcEndID, ins);
                        if (m_isMultiThreadDispatcher && (m_rte == "Posix" || m_rte == "RTOS"))
                            gen->setExternDesbuf(true);
                        gen->setRteStr(m_rtestr);
                        gen->setChip(m_chip, m_srcChipID);
                        gen->setThreadLocal(m_enableThreadLocal);
                        ret = gen->generate();
                        m_infs.emplace_back(gen->getInfName());
                        if (ret)
                            std::cout <<"Generating server code for instance " << gen->getInfName() << " Success!" << ENDL;
                        else
                            CERR <<"Generating server code for instance " << gen->getInfName() << "Failed!" << ENDL;
                    }
                    std::cout << "Generating header code for provider " << ptr->getName() << ENDL;
                    ret = generateStubHeader(ptr);
                    if (ret)
                        std::cout << "Success!" << ENDL;
                    else
                        CERR << "Failed!" << ENDL;
                    std::cout << "Generating source code for provider " << ptr->getName() << ENDL;
                    ret = generateStubSource(ptr);
                    if (ret)
                        std::cout << "Success!" << ENDL;
                    else
                        CERR << "Failed!" << ENDL;
                }
                else if (m_providerType == "Client")
                {
                    std::cout << "Generating " << m_rte << " client code ..." << ENDL;
                    for (const auto &ins : ptr->getElements())
                    {
                        if (!ins || ins->getTag() != "instance")
                            continue;
                        auto gen = std::make_shared<ProxyGenerator>(m_destDir, m_srcEndID, ins);
                        if (m_isMultiThreadDispatcher && (m_rte == "Posix" || m_rte == "RTOS"))
                            gen->setExternDesbuf(true);
                        gen->setRteStr(m_rtestr);
                        gen->setChip(m_chip, m_srcChipID);
                        gen->setThreadLocal(m_enableThreadLocal);
                        ret = gen->generate();
                        m_infs.emplace_back(gen->getInfName());
                        if (ret)
                            std::cout <<"Generating client code for instance " << gen->getInfName() << " Success!" << ENDL;
                        else
                            CERR <<"Generating client code for instance " << gen->getInfName() << "Failed!" << ENDL;
                    }
                    std::cout << "Generating header code for provider " << ptr->getName() << ENDL;
                    ret = generateProxyHeader(ptr);
                    if (ret)
                        std::cout << "Success!" << ENDL;
                    else
                        CERR << "Failed!" << ENDL;
                    std::cout << "Generating source code for provider " << ptr->getName() << ENDL;
                    ret = generateProxySource(ptr);
                    if (ret)
                        std::cout << "Success!" << ENDL;
                    else
                        CERR << "Failed!" << ENDL;
                }

                if (ret)
                    std::cout << "Generation for " << ptr->getName() << " success." << ENDL;
                else
                {
                    CERR << "Generation for " << ptr->getName() << " fail." << ENDL;
                }
            }
        }
    }

    return true;
}

bool MsgBoxGenerator::validateProvider(const std::shared_ptr<FDExtensionRoot> &provider)
{
    if (!provider)
        return false;

    m_providerType.clear();
    m_rte.clear();
    m_srcEndID.clear();

    // get and validate ProviderType
    auto value = provider->getSingleValue("ProviderType");
    if (!value)
    {
        CERR << "No ProviderType defined." << ENDL;
        return false;
    }
    auto type = value->getEnumerator();
    if (!type)
    {
        CERR << "Invalid ProviderType." << ENDL;
        return false;
    }
    m_providerType = type->getName();
    if (m_providerType != "Server" && m_providerType != "Client")
    {
        CERR << "Invalid ProviderType." << ENDL;
        return false;
    }

    // get and validate RTE
    value = provider->getSingleValue("RTE");
    if (!value)
    {
        CERR << "No RTE defined." << ENDL;
        return false;
    }
    type = value->getEnumerator();
    if (!type)
    {
        CERR << "Invalid RTE." << ENDL;
        return false;
    }
    m_rte = type->getName();
    if (m_rte != "BareMetal" && m_rte != "Posix" && m_rte != "LinuxKernel" && m_rte != "RTOS")
    {
        CERR << "Invalid RTE." << ENDL;
        return false;
    }

    // get and validate ChipPlatform
    value = provider->getSingleValue("ChipPlatform");
    if (!value)
    {
        CERR << "No ChipPlatform defined." << ENDL;
        return false;
    }
    type = value->getEnumerator();
    if (!type)
    {
        CERR << "Invalid ChipPlatform." << ENDL;
        return false;
    }
    m_chip = type->getName();

    // get and validate SrcEndID
    value = provider->getSingleValue("SrcEndID");
    if (!value)
    {
        CERR << "No SrcEndID defined." << ENDL;
        return false;
    }
    type = value->getEnumerator();
    if (!type)
    {
        CERR << "Invalid SrcEndID." << ENDL;
        return false;
    }
    m_srcEndID = type->getName();

    if (m_chip == "C1200")
    {
        // get and validate SrcChipID
        m_srcChipID = "0";

        // validate SrcEndID and ChipPlatform.
        if (m_srcEndID != "CPU_0" && m_srcEndID != "CPU_1" && m_srcEndID != "CPU_2" && m_srcEndID != "CPU_3" &&
            m_srcEndID != "CPU_4" && m_srcEndID != "CPU_5" && m_srcEndID != "CPU_6" && m_srcEndID != "CPU_7" &&
            m_srcEndID != "CPUMP2_0" && m_srcEndID != "CPUMP2_1" && m_srcEndID != "ISPCV_0" && m_srcEndID != "ISPCV_1" &&
            m_srcEndID != "ISPCV_2" && m_srcEndID != "ISPCV_3" && m_srcEndID != "ISPCV_4" && m_srcEndID != "NET_0" &&
            m_srcEndID != "DMA_0" && m_srcEndID != "DMA_1" && m_srcEndID != "SWITCH_0" && m_srcEndID != "SWITCH_1" &&
            m_srcEndID != "SWITCH_2" && m_srcEndID != "SWITCH_3" && m_srcEndID != "SWITCH_4" && m_srcEndID != "SWITCH_5" &&
            m_srcEndID != "SECURE_0" && m_srcEndID != "SECURE_1" && m_srcEndID != "SAFETY_0" && m_srcEndID != "SAFETY_1" &&
            m_srcEndID != "REALTIME_0" && m_srcEndID != "REALTIME_1" && m_srcEndID != "REALTIME_2" &&
            m_srcEndID != "REALTIME_3" && m_srcEndID != "REALTIME_4" && m_srcEndID != "REALTIME_5" &&
            m_srcEndID != "MEDIA_0")
        {
            CERR << "Invalid SrcEndID " << m_srcEndID << " with ChipPlatform " << m_chip << "." << ENDL;
            return false;
        }
    }
    else if (m_chip == "A2000")
    {
        // get and validate SrcChipID
        value = provider->getSingleValue("SrcChipID");
        if (!value)
        {
            CERR << "No SrcChipID defined." << ENDL;
            return false;
        }
        type = value->getEnumerator();
        if (!type)
        {
            CERR << "Invalid SrcChipID." << ENDL;
            return false;
        }
        m_srcChipID = type->getName();

        // validate SrcEndID and ChipPlatform.
        if (m_srcEndID != "CPU_0_0" && m_srcEndID != "CPU_0_1" && m_srcEndID != "CPU_0_2" && m_srcEndID != "CPU_0_3" &&
            m_srcEndID != "CPU_1_0" && m_srcEndID != "CPU_1_1" && m_srcEndID != "CPU_1_2" && m_srcEndID != "CPU_1_3" &&
            m_srcEndID != "CPU_2_0" && m_srcEndID != "CPU_2_1" && m_srcEndID != "CPU_2_2" && m_srcEndID != "CPU_2_3" &&
            m_srcEndID != "CPU_3_0" && m_srcEndID != "CPU_3_1" && m_srcEndID != "CPU_3_2" && m_srcEndID != "CPU_3_3" &&
            m_srcEndID != "ISPCV_0" && m_srcEndID != "ISPCV_1" && m_srcEndID != "ISPCV_2" && m_srcEndID != "ISPCV_3" &&
            m_srcEndID != "NET_0" && m_srcEndID != "NET_1" && m_srcEndID != "SECURE_0" && m_srcEndID != "SAFETY_0" &&
            m_srcEndID != "DNPAD_0" && m_srcEndID != "DNPAD_1" && m_srcEndID != "DNPAD_2" && m_srcEndID != "DNPAD_3" &&
            m_srcEndID != "DNPAD_4" && m_srcEndID != "DNPAD_5" && m_srcEndID != "UPPAD_0" && m_srcEndID != "UPPAD_1")
        {
            CERR << "Invalid SrcEndID " << m_srcEndID << " with ChipPlatform " << m_chip << "." << ENDL;
            return false;
        }
    }
    else
    {
        CERR << "Invalid ChipPlatform." << ENDL;
        return false;
    }

    // get and validate FID
    value = provider->getSingleValue("FID");
    if (!value)
    {
        CERR << "No FID defined." << ENDL;
        return false;
    }
    type = value->getEnumerator();
    if (!type)
    {
        CERR << "Invalid FID." << ENDL;
        return false;
    }
    m_fid = type->getName();
    if (m_srcEndID == "CPU_0" || m_srcEndID == "CPU_1" || m_srcEndID == "CPU_2" || m_srcEndID == "CPU_3" ||
        m_srcEndID == "CPU_4" || m_srcEndID == "CPU_5" || m_srcEndID == "CPU_6" || m_srcEndID == "CPU_7")
    {
        if (m_fid != "DEF" && m_fid != "F1" && m_fid != "F2" && m_fid != "F3" && m_fid != "F4" && m_fid != "F5" &&
            m_fid != "F6" && m_fid != "F7")
        {
            CERR << "Invalid FID." << ENDL;
            return false;
        }
    }
    else if (m_srcEndID == "CPUMP2_0" || m_srcEndID == "CPUMP2_1")
    {
        if (m_fid != "DEF" && m_fid != "F1")
        {
            CERR << "Invalid FID." << ENDL;
            return false;
        }
    }
    else
    {
        if (m_fid != "DEF" && m_fid != "F1" && m_fid != "F2" && m_fid != "F3")
        {
            CERR << "Invalid FID." << ENDL;
            return false;
        }
    }

    // get and validate SID.
    value = provider->getSingleValue("SID");
    if (!value)
    {
        CERR << "No SID defined." << ENDL;
        return false;
    }
    if (!value->isInteger())
    {
        CERR << "Invalid SID." << ENDL;
        return false;
    }
    m_sid = value->getInteger();
    if (m_sid < 0 || m_sid > 15)
    {
        CERR << "Invalid SID." << ENDL;
        return false;
    }

    // get and validate EnableMultiThreadDispatcher.
    value = provider->getSingleValue("EnableMultiThreadDispatcher");
    if (!value)
        m_isMultiThreadDispatcher = false;
    else if (value->isBoolean())
        m_isMultiThreadDispatcher = value->getBoolean();

    if (m_isMultiThreadDispatcher)
    {
        // get and validate DispatcherNum.
        value = provider->getSingleValue("DispatcherNum");
        if (!value)
        {
            CERR << "No DispatcherNum defined." << ENDL;
            return false;
        }
        if (!value->isInteger())
        {
            CERR << "Invalid DispatcherNum." << ENDL;
            return false;
        }
        m_dispatcherNum = value->getInteger();

        // get and validate DispatcherBufferLength.
        value = provider->getSingleValue("DispatcherBufferLength");
        if (!value)
        {
            CERR << "No DispatcherBufferLength defined." << ENDL;
            return false;
        }
        if (!value->isInteger())
        {
            CERR << "Invalid DispatcherBufferLength." << ENDL;
            return false;
        }
        m_dispatcherBufferLength = value->getInteger();
    }

    // get and validate EnableRouterRealtimeSched.
    m_enableRouterRealtimeSched = false;
    value = provider->getSingleValue("EnableRouterRealtimeSched");
    if (value && value->isBoolean())
        m_enableRouterRealtimeSched = value->getBoolean();

    if (m_enableRouterRealtimeSched || m_rte == "RTOS")
    {
        // get and validate RouterPriority.
        value = provider->getSingleValue("RouterPriority");
        if (!value)
        {
            CERR << "No RouterPriority defined." << ENDL;
            return false;
        }
        if (!value->isInteger())
        {
            CERR << "Invalid RouterPriority." << ENDL;
            return false;
        }
        m_routerPriority = value->getInteger();

        // get and validate RouterStackSize.
        if (m_rte == "RTOS")
        {
            value = provider->getSingleValue("RouterStackSize");
            if (!value)
            {
                CERR << "No RouterStackSize defined." << ENDL;
                return false;
            }
            if (!value->isInteger())
            {
                CERR << "Invalid RouterStackSize." << ENDL;
                return false;
            }
            m_routerStackSize = value->getInteger();
        }
    }

    // get and validate EnableWorkerRealtimeSched.
    m_enableWorkerRealtimeSched = false;
    value = provider->getSingleValue("EnableWorkerRealtimeSched");
    if (value && value->isBoolean())
        m_enableWorkerRealtimeSched = value->getBoolean();

    if (m_enableWorkerRealtimeSched)
    {
        // get and validate RouterPriority.
        value = provider->getSingleValue("WorkerPriority");
        if (!value)
        {
            CERR << "No RouterPriority defined." << ENDL;
            return false;
        }
        if (!value->isInteger())
        {
            CERR << "Invalid RouterPriority." << ENDL;
            return false;
        }
        m_workerPriority = value->getInteger();
    }

    // get and validate RcvMsgPollingTimes.
    value = provider->getSingleValue("RcvMsgPollingTimes");
    if (!value)
    {
        CERR << "No RcvMsgPollingTimes defined." << ENDL;
        return false;
    }
    if (!value->isInteger())
    {
        CERR << "Invalid RcvMsgPollingTimes." << ENDL;
        return false;
    }
    m_rcvMsgPollingTimes = value->getInteger();

    // get and validate EnableThreadLocal.
    m_enableThreadLocal = false;
    value = provider->getSingleValue("EnableThreadLocal");
    if (value && value->isBoolean())
        m_enableThreadLocal = value->getBoolean();

    // additional check for RTE and scheduler.
    if (m_isMultiThreadDispatcher && (m_rte == "LinuxKernel" || m_rte == "BareMetal"))
    {
        CERR << "Multi-thread dispatcher is not supported in Linux Kernel and BareMetal RTE." << ENDL;
        return false;
    }

    if (m_enableRouterRealtimeSched && m_rte == "BareMetal")
    {
        CERR << "Router realtime scheduler is not supported in BareMetal RTE." << ENDL;
        return false;
    }

    if (m_rte == "RTOS")
    {
        if (m_isMultiThreadDispatcher)
        {
            if (!m_enableRouterRealtimeSched)
            {
                CERR << "EnableRouterRealtimeSched MUST be set, under multithread dispatcher mode in RTOS RTE." << ENDL;
                return false;
            }
            if (!m_enableWorkerRealtimeSched)
            {
                CERR << "EnableWorkerRealtimeSched MUST be set, under multithread dispatcher mode in RTOS RTE." << ENDL;
                return false;
            }
        }
        else
        {
            if (m_enableRouterRealtimeSched)
            {
                CERR << "EnableRouterRealtimeSched MUST NOT be set, under normal mode in RTOS RTE." << ENDL;
                return false;
            }
            if (m_enableWorkerRealtimeSched)
            {
                CERR << "EnableWorkerRealtimeSched MUST NOT be set, under normal mode in RTOS RTE." << ENDL;
                return false;
            }
        }
    }

    if (m_enableWorkerRealtimeSched && !m_enableRouterRealtimeSched)
    {
        CERR << "EnableWorkerRealtimeSched MUST NOT be set, if EnableRouterRealtimeSched is not set." << ENDL;
        return false;
    }

    if (m_enableWorkerRealtimeSched && !m_isMultiThreadDispatcher)
    {
        CERR << "EnableWorkerRealtimeSched MUST NOT be set, if MultiThreadDispatcher is not set." << ENDL;
        return false;
    }

    return true;
}

bool MsgBoxGenerator::generateStubHeader(const std::shared_ptr<FDExtensionRoot> &provider)
{
    if (!provider)
        return false;

    std::string content = R"($LICENSE
$VERSION_COMMENT
#ifndef $HEADER_MACRO
#define $HEADER_MACRO

$INCLUDES
#ifdef __cplusplus
extern "C" {
#endif

struct _$PROVIDER_NAME_t {
$INS_SERVERS
#ifdef IPC_RTE_BAREMETAL
	/**
	 * Receive a message from the server.
	 *
	 * @return 0 if success, negative if fail.
	 */
	int32_t (*receive_message)(void);

	/**
	 * Dispatch a message to the server.
	 *
	 * @return 0 if success, negative if fail.
	 */
	int32_t (*dispatch_message)(void);
#else
	/**
	 * Start the message router.
	 *
	 * @return 0 if success, negative if fail.
	 */
	int32_t (*start)(void);

	/**
	 * Stop the message router.
	 *
	 * @return 0 if success, negative if fail.
	 */
	int32_t (*stop)(void);
#endif
};
#define $PROVIDER_NAME_t struct _$PROVIDER_NAME_t


/**
 *  The internal data used by the server.
 *  Users should define an instance and pass it to the server initialization function.
 */
struct _$PROVIDER_NAME_data_t {
	com_server_data_t com_data;
	$PROVIDER_NAME_t server;
	$INS_SERVER_EXTS
};
#define $PROVIDER_NAME_data_t struct _$PROVIDER_NAME_data_t

/**
 * Initializes the server.
 *
 * @param ins The instance to be initialized.
 * @return A pointer to the initialized server, NULL if fail.
 */
$PROVIDER_NAME_t *$PROVIDER_NAME_init($PROVIDER_NAME_data_t *ins);
/**
 * Destroys the server.
 *
 * @return 0 if success, negetive if fail.
 */
int32_t $PROVIDER_NAME_destroy(void);

#ifdef __cplusplus
}
#endif

#endif // $HEADER_MACRO
)";

    std::string providerName = provider->getName();
    std::string headerMacro = toUpper(providerName) + "_H";
    std::string includes;
    std::string servers;
    std::string serverExts;

    for(auto e : m_infs)
    {
        includes.append("#include \"" + e + "_server.h\"\n");
        servers.append("\t" + e + "_server_t " + e + "_server;\n");
        serverExts.append(e + "_server_ext_t " + e + "_ext;\n\t");
    }
    trim(serverExts);

    // check file
    auto filename = m_folderPath + "/" + providerName + ".h";
    if (std::ifstream(filename))
    {
        //file already exists.
        providerName.append("_provider");
        filename = m_folderPath + "/" + providerName + ".h";
        CERR << "Provider name already used, append with provider" << ENDL;
    }

    replace_all(content, "$LICENSE", getLicense());
    replace_all(content, "$VERSION_COMMENT", getVersionComment());
    replace_all(content, "$HEADER_MACRO", headerMacro);
    replace_all(content, "$INCLUDES", includes);
    replace_all(content, "$INS_SERVERS", servers);
    replace_all(content, "$INS_SERVER_EXTS", serverExts);
    replace_all(content, "$PROVIDER_NAME", providerName);
    replace_all(content, "    ", "\t");
    replace_all(content, "\r\n", "\n");
    replace_all(content, "\t\n", "\n");

    //write file
    std::ofstream ofs(filename, std::ofstream::out | std::ios::binary);
    ofs << content;
    ofs.close();

    return true;
}

std::string MsgBoxGenerator::getMultiThreadDefinitions()
{
    std::string ret;
    if (m_isMultiThreadDispatcher && (m_rte == "Posix" || m_rte == "RTOS"))
    {
        ret = R"(
#include "ipc_lflist_siso.h"
#include <semaphore.h>
#include <stdlib.h>

#define DISPATCH_TASK_NUM $TASK_NUMU
#define MSG_NUM_PER_TASK ($BUFFER_LENU / DISPATCH_TASK_NUM)
#define POLLING_COUNT 5000U

typedef struct _work_data {
    pthread_t tid;
    bool is_working;
    bool is_pending;
#ifdef __LP64__
	uint8_t res[6];
	void *ins;
#else
	uint8_t res[10];
	void *ins;
#endif
    uint32_t working_cnt;
    uint32_t pending_cnt;
    sem_t sem_msg;
    lflist_t used_list;
    lflist_t free_list;
    lflist_node_t msg_nodes[MSG_NUM_PER_TASK];
    des_buf_t msgs[MSG_NUM_PER_TASK];
    serdes_t reply_ser;
}work_data_t;

typedef struct _router_data {
	$PROVIDER_NAME_data_t *ins;
	work_data_t *worker_data;
}router_data_t;
)";
        replace_all(ret, "$TASK_NUM", std::to_string(m_dispatcherNum));
        replace_all(ret, "$BUFFER_LEN", std::to_string(m_dispatcherBufferLength));
    }

    return ret;
}

std::string MsgBoxGenerator::getDispatcherTpl(bool isServer)
{
    std::string ret;
    std::string var_defs;
    std::string ser_init;
    std::string dispatching;
    std::string com_type;
    if (m_isMultiThreadDispatcher && (m_rte == "Posix" || m_rte == "RTOS"))
    {
        ret = R"(static void *dispatch_message(void *arg)
{
	int32_t ret = 0;
	work_data_t *work = (work_data_t *)arg;
	des_buf_t *des = NULL;
    size_t cnt = 0;
    $COM_TYPE *data = NULL;
    $PROVIDER_NAME_data_t *provider = NULL;
$VAR_DEFS
    if (!work)
		return (void *)-ERR_APP_PARAM;

    data = ($COM_TYPE *)work->ins;
    provider = ($PROVIDER_NAME_data_t *)work->ins;
$REINIT_INS
	while (true) {
		if (!data->bRunning)
			break;

		// dequeue
		lflist_node_t *node = lflist_dequeue(&work->used_list);
		if (!node) {
			if (cnt++ < POLLING_COUNT) {
				sched_yield();
				continue;
			} else {
				work->is_pending = true;
				node = lflist_dequeue(&work->used_list);
				if (!node) {
					++work->pending_cnt;
					sem_wait(&work->sem_msg);
					work->is_pending = false;
					continue;
				} else
					work->is_pending = false;
			}
		}
		
		cnt = 0;
		work->is_working = true;
		des = node->data;

		// dispatch message
$DISPATCHING

		// enqueue
		lflist_enqueue(&work->free_list, node);
		work->is_working = false;
		++work->working_cnt;
	}
	return NULL;
}
)";
        std::string reinit_ins;
        std::string init_tpl = "\t(void)$NAME_$TYPE_init(data, NULL, &provider->$NAME_ext);\n";
        std::string type = isServer ? "server" : "client";
        replace_all(init_tpl, "$TYPE", type);
        for (auto e : m_infs)
        {
            std::string init_ins = init_tpl;
            replace_all(init_ins, "$NAME", e);
            reinit_ins.append(init_ins);
        }
        replace_all(ret, "$REINIT_INS", reinit_ins);

        if (isServer)
            ser_init = "\t\t\tser = &work->reply_ser;\n";

    }
    else
    {
        ret = R"(static int32_t dispatch_message(void)
{
	int32_t ret = 0;
	des_buf_t *des = NULL;
    $COM_TYPE *data = ($COM_TYPE *)s_ins;
    $PROVIDER_NAME_data_t *provider = s_ins;
$VAR_DEFS	packet_object_t *packet_obj = NULL;
	lflist_node_t *msg_node = NULL;
	msg_object_t *msg_obj = NULL;
	int8_t* des_data = NULL;
	uintptr_t k_base = 0;
	uintptr_t u_base = 0;

	if (!data || !data->queue)
		return -ERR_APP_PARAM;
	des = &data->des_buf;
	k_base = data->queue->k_addr;
	u_base = (uintptr_t)data->queue;

	packet_obj = KERNEL_2_USER(data->queue->completed_pack_list.tail, k_base, u_base);
	while (packet_obj) {
		msg_node = KERNEL_2_USER(packet_obj->packed_list.tail, k_base, u_base);
		des_data = des->data_buf;

		clear_des_buf(des);
		while (msg_node)
		{
			msg_obj = KERNEL_2_USER(msg_node->data, k_base, u_base);
			ipc_memcpy(des_data, msg_obj->msg.payload, IPC_PAYLOAD_SIZE);
			des_data += IPC_PAYLOAD_SIZE;
			msg_node = KERNEL_2_USER(msg_node->next, k_base, u_base);
		}
		des->header = msg_obj->msg.header;
		des->timestamp = msg_obj->timestamp;
		des->unavail_data_size = IPC_MAX_DATA_SIZE - (msg_obj->msg.header.idx
			* IPC_PAYLOAD_SIZE + msg_obj->msg.header.len * 8);

		// dispatch message
$DISPATCHING

		packet_obj = KERNEL_2_USER(packet_obj->node.next, k_base, u_base);
	}

	return ret;
})";
        if (isServer)
            ser_init = "\t\t\tser = &data->serializer;\n";
    }
    if(isServer)
    {
        com_type = "com_server_data_t";
        var_defs = "\tserdes_t *ser = NULL;\n\tbool need_reply = true;\n";
        dispatching = R"(		need_reply = true;
$INS_SVR_CASES
		if (need_reply) {
$SER_INIT            (void)ipc_ser_init(ser);
            ret = ipc_ser_put_32(ser, (uint32_t *)&ret);
			ser->header = des->header;
			ser->header.cid = des->header.pid;
			ser->header.pid = data->pid;
			ser->header.typ = MSGBX_MSG_TYPE_REPLY;
			if (ret >= 0)
				ret = ipc_ser_finish(ser);
			if (ret >= 0)
				ret = send_reply(data, ser);
			if (ret < 0)
				IPC_LOG_ERR("send reply fail %" PRId32 ".\n", ret);
		})";
    }
    else
    {
        com_type = "com_client_data_t";
        var_defs = "";
        dispatching = R"(        ret = -1;
        $INS_CASES
		if (ret < 0)
			IPC_LOG_ERR("Unexpected message from ID %u.\n", des->header.pid);)";
    }
    replace_all(ret, "$COM_TYPE", com_type);
    replace_all(ret, "$VAR_DEFS", var_defs);
    replace_all(dispatching, "$SER_INIT", ser_init);
    replace_all(ret, "$DISPATCHING", dispatching);
    return ret;
}

std::string MsgBoxGenerator::getRouterTpl(bool isServer)
{
    std::string ret;
    std::string var_defs;
    if (m_isMultiThreadDispatcher && (m_rte == "Posix" || m_rte == "RTOS"))
    {
        ret = R"(static void *router_func(void *arg)
{
	int32_t ret = 0;
	router_data_t *rdata = (router_data_t *)arg;
	$COM_TYPE *data = NULL;
    work_data_t *wdata = NULL;
    lflist_node_t *node = NULL;
	uint8_t index = 0;
	uint64_t max_size = 0;
	size_t min_size = MSG_NUM_PER_TASK;

	// used in dequeue from mapped queue.
	des_buf_t *des = NULL;
	packet_object_t *packet_obj = NULL;
	lflist_node_t *msg_node = NULL;
	msg_object_t *msg_obj = NULL;
	int8_t* des_data = NULL;
	uintptr_t k_base = 0;
	uintptr_t u_base = 0;

    if (!rdata)
        return NULL;
    data = ($COM_TYPE *)rdata->ins;
    wdata = rdata->worker_data;
	if (!wdata || !data || !data->queue)
		return NULL;
	k_base = data->queue->k_addr;
	u_base = (uintptr_t)data->queue;

    //reset s_ins in router thread.
	s_ins = rdata->ins;

	while (data && data->bRunning) {
		ret = receive_message();
		if (ret != 0)
			continue;

		packet_obj = KERNEL_2_USER(data->queue->completed_pack_list.tail, k_base, u_base);
		while (packet_obj) {
			// find index, for min size of used_method_msg_list.
			index = DISPATCH_TASK_NUM;
			max_size = 0;
			for (size_t i = 0; i < DISPATCH_TASK_NUM; ++i) {
				uint64_t size = lflist_size(&wdata[i].free_list);
				// immediate break for empty list.
				if (size == MSG_NUM_PER_TASK) {
					index = i;
					break;
				}
				// calculate min size, and update index.
				if (size > max_size){
					max_size = size;
					index = i;
				}
				if (size < min_size)
					min_size = size;
			}

			// all free lists are empty, search again.
			if (index == DISPATCH_TASK_NUM) {
				sched_yield();
				continue;
			}

			// dequeue
			node = lflist_dequeue(&wdata[index].free_list);
			if (!node)
				continue;

			// get msg
			des = (des_buf_t *)node->data;
			msg_node = KERNEL_2_USER(packet_obj->packed_list.tail, k_base, u_base);
			des_data = des->data_buf;
			clear_des_buf(des);
			while (msg_node)
			{
				msg_obj = KERNEL_2_USER(msg_node->data, k_base, u_base);
				ipc_memcpy(des_data, msg_obj->msg.payload, IPC_PAYLOAD_SIZE);
				des_data += IPC_PAYLOAD_SIZE;
				msg_node = KERNEL_2_USER(msg_node->next, k_base, u_base);
			}
			des->header = msg_obj->msg.header;
			des->timestamp = msg_obj->timestamp;
			des->unavail_data_size = IPC_MAX_DATA_SIZE - (msg_obj->msg.header.idx
				* IPC_PAYLOAD_SIZE + msg_obj->msg.header.len * 8);
			packet_obj = KERNEL_2_USER(packet_obj->node.next, k_base, u_base);

			// enqueue
			lflist_enqueue(&wdata[index].used_list, node);
			// post sig
			if (wdata[index].is_pending)
				sem_post(&wdata[index].sem_msg);
		}
	}
	return arg;
}
)";
        std::string com_type = isServer ? "com_server_data_t" : "com_client_data_t";
        replace_all(ret, "$COM_TYPE", com_type);
    }
    else
    {
        ret = R"(#if defined IPC_RTE_KERNEL
static int router_func(void *arg)
#elif defined IPC_RTE_POSIX
static void *router_func(void *arg)
#elif defined IPC_RTE_RTOS
static void router_func(void *arg)
#else
#error "unknown rte"
#endif
{
	int32_t ret = 0;

#if defined IPC_RTE_POSIX || defined IPC_RTE_RTOS
	while (s_ins && s_ins->com_data.bRunning) {
#elif defined IPC_RTE_KERNEL
	while (unlikely(!kthread_should_stop())) {
#endif
		ret = receive_message();
		if (ret != 0)
			continue;

		ret = dispatch_message();
		if (ret < 0)
			continue;
	}
#if defined IPC_RTE_KERNEL
	return RESULT_SUCCESS;
#elif defined IPC_RTE_POSIX
    return arg;
#elif defined IPC_RTE_RTOS
	return;
#else
#error "unknown rte"
#endif
}
)";
    }

    return ret;
}

std::string MsgBoxGenerator::getStartTpl(bool isServer)
{
    std::string ret;
    std::string var_defs;
    if (m_isMultiThreadDispatcher && (m_rte == "Posix" || m_rte == "RTOS"))
    {
        ret = R"(static int32_t start_work_thread(work_data_t *work, void *(*func)(void *))
{
    int32_t ret = 0;
$WORKER_SCHED_VARS
    if (!work)
        return -ERR_APP_PARAM;

    // init lists
    lflist_init(&work->free_list);
    lflist_init(&work->used_list);
    // push nodes
    for (size_t i = 0; i < MSG_NUM_PER_TASK; ++i) {
        lflist_init_node(&work->msg_nodes[i], &work->msgs[i]);
        lflist_enqueue(&work->free_list, &work->msg_nodes[i]);
    }
    // create semaphores
    sem_init(&work->sem_msg, 0, 0);
	// fill provider's data instance.
	work->ins = s_ins;
    // create dispatch task
$WORKER_SCHED_SET	ret = pthread_create(&work->tid, NULL, func, work);
    if (ret != 0)
        return -ERR_APP_START;
    
    return RESULT_SUCCESS;
}

static int32_t stop_work_thread(work_data_t *work)
{
    int32_t ret = 0;
    if (!work)
        return -ERR_APP_PARAM;

    if (work->is_pending)
        sem_post(&work->sem_msg);
    ret = pthread_join(work->tid, NULL);
    if (ret != 0)
        return -ERR_APP_STOP;

    sem_destroy(&work->sem_msg);
    
    return RESULT_SUCCESS;
}

static int32_t start(void)
{
$ROUTER_SCHED_VARS	int32_t ret = 0;
	static $THREAD_LOCAL work_data_t worker_data[DISPATCH_TASK_NUM] = {0};
	static $THREAD_LOCAL router_data_t router_data = {0};
$VAR_DEFS
	if (!data)
		return ERR_APP_PARAM;

	if (data->bRunning)
		return RESULT_SUCCESS;

	data->bRunning = true;

    // start worker thread
	for (size_t i = 0; i < DISPATCH_TASK_NUM; ++i) {
		ret = start_work_thread(&worker_data[i], dispatch_message);
		if (ret != RESULT_SUCCESS) {
			data->bRunning = false;
			return -ERR_APP_START;
		}
	}

	// create route task
	router_data.ins = s_ins;
	router_data.worker_data = worker_data;
$ROUTER_SCHED_SET    ret = pthread_create(&data->route_task, NULL, router_func, &router_data);
	if (ret != 0) {
		data->bRunning = false;
		return -ERR_APP_START;
	}

	return RESULT_SUCCESS;
}
)";
    }
    else
    {
        ret = R"(static int32_t start(void)
{
#if defined IPC_RTE_POSIX
	int32_t ret = 0;
#elif defined IPC_RTE_RTOS
    static static_tcb_t tcb_buffer;
    static stack_type_t stack_buffer[$STACK_SIZE];
#endif
$ROUTER_SCHED_VARS$VAR_DEFS
	if (!data)
		return ERR_APP_PARAM;

	if (data->bRunning)
		return RESULT_SUCCESS;

	data->bRunning = true;
#if defined IPC_RTE_POSIX
$ROUTER_SCHED_SET	ret = pthread_create(&data->route_task, NULL, router_func, NULL);
	if (ret != 0) {
#elif defined IPC_RTE_RTOS
	data->route_task = TaskCreate((task_func_t)router_func, "$PROVIDER_NAME_thread", $STACK_SIZE, NULL,
        $PRIORITY, (uint8_t *)stack_buffer, &tcb_buffer, true);
	if (data->route_task == 0) {
#elif defined IPC_RTE_KERNEL
	data->route_task = kthread_run(router_func, NULL, "$PROVIDER_NAME_thread");
	if (unlikely(!data->route_task)) {
#else
#error "unknown rte"
#endif
		data->bRunning = false;
		return -ERR_APP_START;
	}
$ROUTER_SCHED_EXT
	return RESULT_SUCCESS;
}
)";
    }

    std::string routerSchedVar;
    std::string workerSchedVar;
    std::string workerSchedSet;
    std::string routerSchedSet;
    std::string routerSchedExt;
    if (m_enableRouterRealtimeSched)
    {
        if (m_rte == "Posix")
        {
            routerSchedVar = R"(    struct sched_param param = {0};
    pthread_attr_t attr = {0};
    int policy = SCHED_RR;
)";
            routerSchedSet = R"(    pthread_attr_init(&attr);
    pthread_attr_setschedpolicy(&attr, policy);
    param.sched_priority = $PRIORITY;
    pthread_attr_setschedparam(&attr, &param);
)";
            replace_all(ret, "pthread_create(&data->route_task, NULL, router_func,",
                "pthread_create(&data->route_task, &attr, router_func,");
        }
        else if (m_rte == "RTOS")
        {
            routerSchedVar = R"(    struct sched_param param = {0};
    pthread_attr_t attr = {0};
)";
            routerSchedSet = R"(    pthread_attr_init(&attr);
    param.sched_priority = $PRIORITY;
    pthread_attr_setschedparam(&attr, &param);
	pthread_attr_setstacksize(&attr, 1024);
)";
            replace_all(ret, "pthread_create(&data->route_task, NULL, router_func,",
                "pthread_create(&data->route_task, &attr, router_func,");
        }
        else if (m_rte == "LinuxKernel")
        {
            routerSchedVar = R"(    struct sched_param param = {0};
)";
            routerSchedExt = R"(#if defined IPC_RTE_KERNEL
    param.sched_priority = $PRIORITY;
    sched_setscheduler(data->route_task, SCHED_FIFO, &param);
#endif
)";
        }
    }
    if (m_enableWorkerRealtimeSched)
    {
        if (m_rte == "Posix")
        {
            workerSchedVar = R"(    struct sched_param param = {0};
pthread_attr_t attr = {0};
int policy = SCHED_RR;
)";
            workerSchedSet = R"(    pthread_attr_init(&attr);
pthread_attr_setschedpolicy(&attr, policy);
param.sched_priority = $PRIORITY;
pthread_attr_setschedparam(&attr, &param);
)";
        }
        else if (m_rte == "RTOS")
        {
            workerSchedVar = R"(    struct sched_param param = {0};
    pthread_attr_t attr = {0};
)";
            workerSchedSet = R"(    pthread_attr_init(&attr);
    param.sched_priority = $PRIORITY;
    pthread_attr_setschedparam(&attr, &param);
	pthread_attr_setstacksize(&attr, 1024);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
)";            
        }
        replace_all(workerSchedSet, "$PRIORITY", std::to_string(m_workerPriority));
        replace_all(ret, "ret = pthread_create(&work->tid, NULL, func, work);",
            "ret = pthread_create(&work->tid, &attr, func, work);");
    }

    replace_all(ret, "$ROUTER_SCHED_VARS", routerSchedVar);
    replace_all(ret, "$ROUTER_SCHED_SET", routerSchedSet);
    replace_all(ret, "$ROUTER_SCHED_EXT", routerSchedExt);
    replace_all(ret, "$WORKER_SCHED_VARS", workerSchedVar);
    replace_all(ret, "$WORKER_SCHED_SET", workerSchedSet);

    if(isServer)
    {
        var_defs = "\tcom_server_data_t *data = (com_server_data_t *)s_ins;\n";
    }
    else
    {
        var_defs = "\tcom_client_data_t *data = (com_client_data_t *)s_ins;\n";
    }

    replace_all(ret, "$VAR_DEFS", var_defs);
    replace_all(ret, "$STACK_SIZE", std::to_string(m_routerStackSize));
    replace_all(ret, "$PRIORITY", std::to_string(m_routerPriority));

    return ret;
}

std::string MsgBoxGenerator::getStopTpl(bool isServer)
{
    std::string ret;
    std::string var_defs;
    if (m_isMultiThreadDispatcher && (m_rte == "Posix" || m_rte == "RTOS"))
    {
        ret = R"(static int32_t stop(void)
{
	int32_t ret = 0;
    router_data_t *router_data = NULL;
$VAR_DEFS
	if (!data)
		return ERR_APP_PARAM;

	if (!data->bRunning)
		return RESULT_SUCCESS;

	//sleep 1 seconds.
	sleep(1);
	data->bRunning = false;
	ipc_trans_layer_release_recv_wait(data->pid, data->handle);
	ret = pthread_join(data->route_task, (void **)&router_data);
	if (ret != 0 || !router_data || !router_data->worker_data)
		return -ERR_APP_STOP;

	for(size_t i = 0; i < DISPATCH_TASK_NUM; ++i)
		stop_work_thread(&router_data->worker_data[i]);

	return RESULT_SUCCESS;
}
)";
    }
    else
    {
        ret = R"(static int32_t stop(void)
{
	int32_t ret = 0;
$VAR_DEFS

	if (!data)
		return ERR_APP_PARAM;

	if (!data->bRunning)
		return RESULT_SUCCESS;

#if defined IPC_RTE_POSIX
	sleep(1);
	data->bRunning = false;
	ipc_trans_layer_release_recv_wait(data->pid, data->handle);
	ret = pthread_join(data->route_task, NULL);
	if (ret != 0)
		return -ERR_APP_STOP;
#elif defined IPC_RTE_RTOS
	Msleep(1000);
	data->bRunning = false;
	ipc_trans_layer_release_recv_wait(data->pid, data->handle);
    TaskDelete(data->route_task);
#elif defined IPC_RTE_KERNEL
	msleep(1000);
	if (likely(data->route_task)) {
		ipc_trans_layer_release_recv_wait(data->pid, data->handle);
		ret = kthread_stop(data->route_task);
		if (unlikely(ret))
			return -ERR_APP_STOP;
	}
	data->bRunning = false;
#else
#error "unknown rte"
#endif

	return RESULT_SUCCESS;
}
)";
    }
    if(isServer)
    {
        var_defs = "\tcom_server_data_t *data = (com_server_data_t *)s_ins;\n";
    }
    else
    {
        var_defs = "\tcom_client_data_t *data = (com_client_data_t *)s_ins;\n";
    }
    replace_all(ret, "$VAR_DEFS", var_defs);
    return ret;
}

bool MsgBoxGenerator::generateStubSource(const std::shared_ptr<FDExtensionRoot> &provider)
{
    if (!provider)
        return false;

    std::string content = R"($LICENSE
$VERSION_COMMENT
#include "$PROVIDER_NAME.h"
#ifdef IPC_RTE_KERNEL
#include <bst/ipc_trans_common.h>
#include <bst/ipc_trans_layer.h>
#else
#include "ipc_trans_common.h"
#include "ipc_trans_layer.h"
#endif

#define PID $PID_VALUE
#define FID $FID_VALUE
#define SID $SID_VALUE
$MT_DISPATCHER_DEFS
static $THREAD_LOCAL $PROVIDER_NAME_data_t *s_ins = NULL;

// receive messages
static int32_t receive_message(void)
{
	com_server_data_t *data = (com_server_data_t *)s_ins;

	if (!data)
		return -ERR_APP_PARAM;

	return ipc_trans_layer_query_info(data->pid, data->handle, $POLLING_TIMES, 0);
}

// dispatch messages
$DISPATCH_FUNC
#ifndef IPC_RTE_BAREMETAL
// router function
$ROUTER_FUNC
// start message router
$START_FUNC
// stop message router.
$STOP_FUNC
#endif
// $PROVIDER_NAME_init
$PROVIDER_NAME_t *$PROVIDER_NAME_init($PROVIDER_NAME_data_t *ins)
{
	int32_t ret = 0;
	com_server_data_t *data = (com_server_data_t *)ins;

	if (!data)
		return NULL;
	if (s_ins && s_ins->com_data.initialized) {
		IPC_LOG_ERR("already initialized.\n");
		return &s_ins->server;
	}

	s_ins = ins;

	data->pid = data->pid == 0 ? PID : data->pid;
	data->fid = data->fid == 0 ? FID : data->fid;
	data->sid = data->sid == 0 ? SID : data->sid;

	// create server handle.
	ret = ipc_trans_layer_stub_create_handle(data->pid, data->fid, data->sid,
                data->pid, &data->handle);
	if (ret < 0)
	{
		IPC_LOG_ERR("create handle fail %" PRId32 ".\n", ret);
		return NULL;
	}

	// init servers
$INS_SVR_INIT
	
    // map session queues.
    ret = ipc_trans_layer_mmap_session(data->pid, data->handle, (void**)&ins->com_data.queue);
	if (ret < 0)
	{
		IPC_LOG_ERR("mmap session addr fail %" PRId32 ".\n", ret);
		return NULL;
	}
#ifdef IPC_RTE_BAREMETAL
	ins->server.receive_message = receive_message;
	ins->server.dispatch_message = dispatch_message;
#else
	ins->server.start = start;
	ins->server.stop = stop;
#endif
    IPC_MUTEX_INIT(&data->send_mtx);
	data->initialized = true;
	return &ins->server;
}

// $PROVIDER_NAME_destroy
int32_t $PROVIDER_NAME_destroy(void)
{
	int32_t ret = 0;
	com_server_data_t *data = (com_server_data_t *)s_ins;

	// if is NULL, just return SUCCESS.
	if (!data)
		return RESULT_SUCCESS;

	ret = ipc_trans_layer_unregister_method(data->pid, data->handle);
	if (ret < 0)
		return ret;
	ret = ipc_trans_layer_destroy_handle(data->pid, data->handle);
	if (ret < 0)
		return ret;

$INS_DESTROY
    IPC_MUTEX_DESTROY(&data->send_mtx);
	ipc_memset(s_ins, 0, sizeof($PROVIDER_NAME_data_t));
	s_ins = NULL;
	return ret;
}
)";

    std::string providerName = provider->getName();
    std::string headerMacro = toUpper(providerName) + "_H";
    std::string includes;
    std::list<std::string> serverCasesList;
    std::string serverCases;
    std::string serverInits;
    std::string insDestroy;

    std::string mtDefs = getMultiThreadDefinitions();
    std::string dispatcherTpl = getDispatcherTpl(true);
    std::string routerTpl = getRouterTpl(true);
    std::string startTpl = getStartTpl(true);
    std::string stopTpl = getStopTpl(true);
    std::string caseTpl = "\t\tret = provider->server.$NAME_server.dispatch_request(des, &need_reply);\n";

    std::string casesDispatchLogTpl = R"(        if (ret < 0)
            IPC_LOG_ERR("Server dispatch request failed %" PRId32 ".\n", ret);
)";
    std::string initTpl = R"(    ret = $NAME_server_init(data, &ins->server.$NAME_server, &ins->$NAME_ext);
	if (ret < 0) {
		(void)ipc_trans_layer_unregister_method(data->pid, data->handle);
		(void)ipc_trans_layer_destroy_handle(data->pid, data->handle);
		return NULL;
	}
)";
    std::string dtorTpl = R"(	$NAME_server_destroy();
)";
    for(auto e : m_infs)
    {
        std::string svrCase = caseTpl;
        replace_all(svrCase, "$NAME", e);
        serverCasesList.emplace_back(svrCase);

        std::string svrInit = initTpl;
        replace_all(svrInit, "$NAME", e);
        serverInits.append(svrInit);

        std::string dtor = dtorTpl;
        replace_all(dtor, "$NAME", e);
        insDestroy.append(dtor);
    }
    if (!serverCasesList.empty())
    {
        serverCases = join(serverCasesList, "        if (ret < 0)\n\t");
        serverCases.append(casesDispatchLogTpl);
    }

    // check file
    auto filename = m_folderPath + "/" + providerName + ".c";
    if (std::ifstream(filename))
    {
        //file already exists.
        providerName.append("_provider");
        filename = m_folderPath + "/" + providerName + ".c";
        CERR << "Provider name already used, append with provider" << ENDL;
    }

    replace_all(content, "$LICENSE", getLicense());
    // replace license comment style to pass checkpatch.pl
    replace_one(content, "/* SPDX-License-Identifier: GPL-2.0 OR Apache 2.0\n *", "// SPDX-License-Identifier: GPL-2.0 OR Apache 2.0\n/*");
    replace_all(content, "$VERSION_COMMENT", getVersionComment());
    if (m_rte == "LinuxKernel")
        replace_one(content, "$PROVIDER_NAME_data_t *s_ins = NULL;", "$PROVIDER_NAME_data_t *s_ins;");

    replace_all(content, "$PID_VALUE", m_srcEndID);
    replace_all(content, "$FID_VALUE", m_fid);
    replace_all(content, "$SID_VALUE", std::to_string(m_sid).append("U"));
    replace_all(content, "$MT_DISPATCHER_DEFS", mtDefs);
    replace_all(content, "$DISPATCH_FUNC", dispatcherTpl);
    replace_all(content, "$ROUTER_FUNC", routerTpl);
    replace_all(content, "$START_FUNC", startTpl);
    replace_all(content, "$STOP_FUNC", stopTpl);
    replace_all(content, "$INS_SVR_CASES", serverCases);
    replace_all(content, "$INS_SVR_INIT", serverInits);
    replace_all(content, "$INS_DESTROY", insDestroy);
    replace_all(content, "$PROVIDER_NAME", providerName);
    replace_all(content, "$POLLING_TIMES", std::to_string(m_rcvMsgPollingTimes));
    if (m_enableThreadLocal)
        replace_all(content, " $THREAD_LOCAL", " __thread");
    else
        replace_all(content, " $THREAD_LOCAL", "");

    replace_all(content, "    ", "\t");
    replace_all(content, "\r\n", "\n");
    replace_all(content, "\t\n", "\n");
    // write file
    std::ofstream ofs(filename, std::ofstream::out | std::ios::binary);
    ofs << content;
    ofs.close();

    return true;
}

bool MsgBoxGenerator::generateProxyHeader(const std::shared_ptr<FDExtensionRoot> &provider)
{
    if (!provider)
        return false;

    std::string content = R"($LICENSE
$VERSION_COMMENT
#ifndef $HEADER_MACRO
#define $HEADER_MACRO

$INCLUDES
#ifdef __cplusplus
extern "C" {
#endif

struct _$PROVIDER_NAME_t {
$INS_CLIENTS
#ifdef IPC_RTE_BAREMETAL
	/**
	 * Receive a message from the server.
	 *
	 * @return 0 if success, negative if fail.
	 */
	int32_t (*receive_message)(void);

	/**
	 * Dispatch a message to the server.
	 *
	 * @return 0 if success, negative if fail.
	 */
	int32_t (*dispatch_message)(void);
#else
	/**
	 * Start the message router.
	 *
	 * @return 0 if success, negative if fail.
	 */
	int32_t (*start)(void);

	/**
	 * Stop the message router.
	 *
	 * @return 0 if success, negative if fail.
	 */
	int32_t (*stop)(void);
#endif
};
#define $PROVIDER_NAME_t struct _$PROVIDER_NAME_t

/**
 *  The internal data used by the client.
 *  Users should define an instance and pass it to the initialization function.
 */
struct _$PROVIDER_NAME_data_t {
	com_client_data_t com_data;
	$PROVIDER_NAME_t client;
	$INS_CLIENT_EXTS
};
#define $PROVIDER_NAME_data_t struct _$PROVIDER_NAME_data_t

/**
 * Initializes the client.
 *
 * @param data The data to be used by the client.
 * @return A pointer to the initialized client, NULL if fail.
 */
$PROVIDER_NAME_t *$PROVIDER_NAME_init($PROVIDER_NAME_data_t *ins);

/**
 * Destroys the client.
 *
 * @return 0 if success, negetive if fail.
 */
int32_t $PROVIDER_NAME_destroy(void);

#ifdef __cplusplus
}
#endif

#endif // $HEADER_MACRO
)";

    std::string providerName = provider->getName();
    std::string headerMacro = toUpper(providerName) + "_H";
    std::string includes;
    std::string servers;
    std::string serverExts;

    for(auto e : m_infs)
    {
        includes.append("#include \"" + e + "_client.h\"\n");
        servers.append("\t" + e + "_client_t " + e + "_client;\n");
        serverExts.append(e + "_client_ext_t " + e + "_ext;\n\t");
    }
    trim(serverExts);

    // check file
    auto filename = m_folderPath + "/" + providerName + ".h";
    if (std::ifstream(filename))
    {
        //file already exists.
        providerName.append("_provider");
        filename = m_folderPath + "/" + providerName + ".h";
        CERR << "Provider name already used, append with provider" << ENDL;
    }

    replace_all(content, "$LICENSE", getLicense());
    replace_all(content, "$VERSION_COMMENT", getVersionComment());
    replace_all(content, "$HEADER_MACRO", headerMacro);
    replace_all(content, "$INCLUDES", includes);
    replace_all(content, "$INS_CLIENTS", servers);
    replace_all(content, "$INS_CLIENT_EXTS", serverExts);
    replace_all(content, "$PROVIDER_NAME", providerName);
    replace_all(content, "    ", "\t");
    replace_all(content, "\r\n", "\n");
    replace_all(content, "\t\n", "\n");
    // write file
    std::ofstream ofs(filename, std::ofstream::out | std::ios::binary);
    ofs << content;
    ofs.close();

    return true;
}
bool MsgBoxGenerator::generateProxySource(const std::shared_ptr<FDExtensionRoot> &provider)
{
    if (!provider)
        return false;

    std::string content = R"($LICENSE
$VERSION_COMMENT
#include "$PROVIDER_NAME.h"

#define PID $PID_VALUE
#define FID $FID_VALUE
#define SID $SID_VALUE
$MT_DISPATCHER_DEFS
static $THREAD_LOCAL $PROVIDER_NAME_data_t *s_ins = NULL;

// receive messages
static int32_t receive_message(void)
{
	int32_t ret = 0;
	sts_endmap_t map = {0};
	uint64_t chip_map = 0;
	bool status = false;
	com_client_data_t *data = (com_client_data_t *)s_ins;
$INS_EXT_VARS
	if (!data)
		return -ERR_APP_PARAM;

	ret = ipc_trans_layer_query_info(data->pid, data->handle, $POLLING_TIMES, 0);

	// check if availability changed
	if (ret == QUERY_INFO_DST_STS_OFFLINE) {
		if ($MAIN_EXT->avail_changed_cb)
			$MAIN_EXT->avail_changed_cb(false, $MAIN_EXT->avail_ext);
	} else if (ret == QUERY_INFO_DST_STS_ONLINE) {
		if ($MAIN_EXT->avail_changed_cb)
			$MAIN_EXT->avail_changed_cb(true, $MAIN_EXT->avail_ext);
	} else if (ret == QUERY_INFO_DST_STS_CHANGED) {
		ret = ipc_trans_layer_get_endmap(data->pid, &map);
		if (ret == 0) {
$INS_EXT_CHECKS
		}
	}

	return ret;
}

// dispatch messages
$DISPATCH_FUNC
#ifndef IPC_RTE_BAREMETAL
// router function
$ROUTER_FUNC
// start message router
$START_FUNC
// stop message router.
$STOP_FUNC
#endif
// $PROVIDER_NAME_init
$PROVIDER_NAME_t *$PROVIDER_NAME_init($PROVIDER_NAME_data_t *ins)
{
	int32_t ret = 0;
	com_client_data_t *data = (com_client_data_t *)ins;

	if (!data)
		return NULL;
	if (s_ins && s_ins->com_data.initialized) {
		IPC_LOG_ERR("already initialized.\n");
		return &s_ins->client;
	}

	s_ins = ins;

	data->pid = data->pid == 0 ? PID : data->pid;
	data->fid = data->fid == 0 ? FID : data->fid;
	data->sid = data->sid == 0 ? SID : data->sid;

	// init clients
$INS_INIT
	// create client handle.
	ret = ipc_trans_layer_proxy_create_handle(data->pid, data->fid, data->sid, $CID, $CCID, &data->handle);
	if (ret < 0)
	{
		IPC_LOG_ERR("create handle fail %" PRId32 ".\n", ret);
		return NULL;
	}

	ret = ipc_trans_layer_mmap_session(data->pid, data->handle, (void**)&ins->com_data.queue);
	if (ret < 0)
	{
		IPC_LOG_ERR("mmap session addr fail %" PRId32 ".\n", ret);
		return NULL;
	}

#ifdef IPC_RTE_BAREMETAL
	ins->client.receive_message = receive_message;
	ins->client.dispatch_message = dispatch_message;
#else
	ins->client.start = start;
	ins->client.stop = stop;
#endif
    IPC_MUTEX_INIT(&data->send_mtx);
	init_registry_list(data->common_registry, IPC_TOKEN_NUM);
	data->initialized = true;
	return &ins->client;
}

// $PROVIDER_NAME_destroy
int32_t $PROVIDER_NAME_destroy(void)
{
	int32_t ret = 0;
	com_client_data_t *data = (com_client_data_t *)s_ins;

	// if is NULL, just return SUCCESS.
	if (!data)
		return RESULT_SUCCESS;

	ret = ipc_trans_layer_destroy_handle(data->pid, data->handle);
	if (ret < 0)
		return ret;

$INS_DESTROY
    IPC_MUTEX_DESTROY(&data->send_mtx);
	destroy_registry_list(data->common_registry, IPC_TOKEN_NUM);
	ipc_memset(s_ins, 0, sizeof($PROVIDER_NAME_data_t));
	s_ins = NULL;
	return ret;
}
)";

    std::string providerName = provider->getName();
    std::string headerMacro = toUpper(providerName) + "_H";
    std::string includes;
    std::string insCases;
    std::string insInits;
    std::string insDestroy;
    std::string insExtVars;
    std::string insExtChecks;
    std::string mainExt = m_infs.front() + "_ext";
    std::string dispatcherTpl = getDispatcherTpl(false);
    std::string routerTpl = getRouterTpl(false);
    std::string startTpl = getStartTpl(false);
    std::string stopTpl = getStopTpl(false);
    std::string caseTpl = R"(		if (des->header.pid == provider->$NAME_ext.cid) {
			if (des->header.typ == MSGBX_MSG_TYPE_BROADCAST)
                ret = provider->client.$NAME_client.dispatch_broadcast(des);
            else if (des->header.typ == MSGBX_MSG_TYPE_REPLY)
                ret = provider->client.$NAME_client.dispatch_reply(des);
        }
)";
    std::string initTpl = R"(	ret = $NAME_client_init(data, &ins->client.$NAME_client, &ins->$NAME_ext);
	if (ret < 0)
		return NULL;
)";
    for(auto e : m_infs)
    {
        std::string svrCase = caseTpl;
        replace_all(svrCase, "$NAME", e);
        insCases.append(svrCase);

        std::string svrInit = initTpl;
        replace_all(svrInit, "$NAME", e);
        insInits.append(svrInit);

        std::string dtor = "\t$NAME_client_destroy();\n";
        replace_all(dtor, "$NAME", e);
        insDestroy.append(dtor);

        std::string extVar = "\t$NAME_client_ext_t *$NAME_ext = &s_ins->$NAME_ext;\n";
        replace_all(extVar, "$NAME", e);
        insExtVars.append(extVar);

        std::string extCheck = R"(			chip_map = $NAME_ext->ccid != 0 ? map.hi_map : map.lo_map;
			status = (chip_map & $NAME_ext->cid_mask) != 0 ? true : false;
			if (status != $NAME_ext->status) {
				$NAME_ext->status = status;
				if ($NAME_ext->avail_changed_cb)
					$NAME_ext->avail_changed_cb(status, $NAME_ext->avail_ext);
			}
)";
        replace_all(extCheck, "$NAME", e);
        insExtChecks.append(extCheck);
    }
    trim(insCases);

    if (m_infs.size() == 1)
    {
        std::string dst = "ins->" + m_infs.front() + "_ext.cid";
        replace_all(content, "$CID", dst);
        dst = "ins->" + m_infs.front() + "_ext.ccid";
        replace_all(content, "$CCID", dst);
    }
    else 
    {
        replace_all(content, "$CID", "MULTI_DST_CLIENT");
        replace_all(content, "$CCID", "MULTI_DST_CLIENT");
        replace_all(insCases, "_client.dispatch_broadcast(des);\n			if (des->header.pid ",
                    "_client.dispatch_broadcast(des);\n			if (ret < 0 && des->header.pid ");
    }
    // check file
    auto filename = m_folderPath + "/" + providerName + ".c";
    if (std::ifstream(filename))
    {
        //file already exists.
        providerName.append("_provider");
        filename = m_folderPath + "/" + providerName + ".c";
        CERR << "Provider name already used, append with provider" << ENDL;
    }

    replace_all(content, "$LICENSE", getLicense());
    // replace license comment style to pass checkpatch.pl
    replace_one(content, "/* SPDX-License-Identifier: GPL-2.0 OR Apache 2.0\n *", "// SPDX-License-Identifier: GPL-2.0 OR Apache 2.0\n/*");

    replace_all(content, "$VERSION_COMMENT", getVersionComment());

    if (m_rte == "LinuxKernel")
        replace_one(content, "$PROVIDER_NAME_data_t *s_ins = NULL;", "$PROVIDER_NAME_data_t *s_ins;");

    replace_all(content, "$PID_VALUE", m_srcEndID);
    replace_all(content, "$FID_VALUE", m_fid);
    replace_all(content, "$SID_VALUE", std::to_string(m_sid).append("U"));
    replace_all(content, "$MT_DISPATCHER_DEFS", getMultiThreadDefinitions());
    replace_all(dispatcherTpl, "$INS_CASES", insCases);
    replace_all(content, "$DISPATCH_FUNC", dispatcherTpl);
    replace_all(content, "$ROUTER_FUNC", routerTpl);
    replace_all(content, "$START_FUNC", startTpl);
    replace_all(content, "$STOP_FUNC", stopTpl);
    replace_all(content, "$INS_INIT", insInits);
    replace_all(content, "$INS_DESTROY", insDestroy);
    replace_all(content, "$INS_EXT_VARS", insExtVars);
    replace_all(content, "$INS_EXT_CHECKS", insExtChecks);
    replace_all(content, "$MAIN_EXT", mainExt);
    replace_all(content, "$PROVIDER_NAME", providerName);
    replace_all(content, "$POLLING_TIMES", std::to_string(m_rcvMsgPollingTimes));
    if (m_enableThreadLocal)
        replace_all(content, " $THREAD_LOCAL", " __thread");
    else
        replace_all(content, " $THREAD_LOCAL", "");
    replace_all(content, "    ", "\t");
    replace_all(content, "\r\n", "\n");
    replace_all(content, "\t\n", "\n");
    // write file
    std::ofstream ofs(filename, std::ofstream::out | std::ios::binary);
    ofs << content;
    ofs.close();

    return true;
}

std::string MsgBoxGenerator::getVersionComment()
{
    std::string ret =  R"(
/* This file is auto generated for message box v$VERSION.
 * All manual modifications will be LOST by next generation.
 * It is recommended NOT modify it.
 */
)";
    replace_all(ret, "$VERSION", MsgBoxGenerator::version());
    return ret;
}

} // namespace BstIdl